/*
 *  Copyright 2019-2020 Zheng Jie
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *  http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */
package com.dx.financehy.exception.handler;

import cn.hutool.core.util.StrUtil;
import com.dx.financehy.exception.BadRequestException;
import com.dx.financehy.exception.EntityExistException;
import com.dx.financehy.exception.EntityNotFoundException;
import com.dx.financehy.exception.MyAuthException;
import com.dx.financehy.utils.R;
import lombok.extern.slf4j.Slf4j;
import org.springframework.dao.DataIntegrityViolationException;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.validation.BindException;
import org.springframework.validation.FieldError;
import org.springframework.web.HttpMediaTypeNotSupportedException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.MissingServletRequestParameterException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException;
import org.springframework.web.multipart.support.MissingServletRequestPartException;

import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;
import java.sql.SQLException;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;

/**
 * @author Zheng Jie
 * @date 2018-11-23
 */
@Slf4j
@RestControllerAdvice
public class GlobalExceptionHandler {

    /**
     * 处理自定义异常(仅仅针对auth出现异常时的拦截处理)
     */
    @ExceptionHandler(value = MyAuthException.class)
    public ResponseEntity<Object> myAuthException(MyAuthException e) {
        // 打印堆栈信息
        return new ResponseEntity<>(e.getMessage(),HttpStatus.UNAUTHORIZED);
    }

    /**
     * 处理所有不可知的异常
     */
    @ExceptionHandler(Throwable.class)
    public R<?> handleException(Throwable e) {
        // 打印堆栈信息
        log.error("Throwable:", e);
        return R.error(e.getMessage());
    }

    /**
     * BadCredentialsException
     */
    @ExceptionHandler(BadCredentialsException.class)
    public R<?> badCredentialsException(BadCredentialsException e) {
        // 打印堆栈信息
        log.error("坏的凭证",e);
        return R.error("用户名或密码不正确" );
    }

    /**
     * 处理自定义异常
     */
    @ExceptionHandler(value = BadRequestException.class)
    public R<?> badRequestException(BadRequestException e) {
        // 打印堆栈信息
        log.error("BadRequestException:", e);
        return R.error(e.getMessage());
    }

    /**
     * 处理 EntityExist
     */
    @ExceptionHandler(value = EntityExistException.class)
    public R<?> entityExistException(EntityExistException e) {
        // 打印堆栈信息
        log.error("EntityExistException:", e);
        return R.error(e.getMessage());
    }

    /**
     * 处理 EntityNotFound
     */
    @ExceptionHandler(value = EntityNotFoundException.class)
    public R<?> entityNotFoundException(EntityNotFoundException e) {
        // 打印堆栈信息
        log.error("EntityNotFoundException:", e);
        return R.error(e.getMessage());
    }

    /**
     * 处理所有接口数据验证异常
     */
    @ExceptionHandler(MethodArgumentNotValidException.class)
    public R<?> handleMethodArgumentNotValidException(MethodArgumentNotValidException e) {
        // 打印堆栈信息
        log.error("MethodArgumentNotValidException:", e);
        String[] str = Objects.requireNonNull(e.getBindingResult().getAllErrors().get(0).getCodes())[1].split("\\.");
        String message = e.getBindingResult().getAllErrors().get(0).getDefaultMessage();
        String msg = "不能为空";
        if (msg.equals(message)) {
            message = str[1] + ":" + message;
        }
        return R.error(message);
    }

    @ExceptionHandler(ConstraintViolationException.class)
    public R<?> constraintViolationException(ConstraintViolationException ex) {
        log.error("ConstraintViolationException:", ex);
        Set<ConstraintViolation<?>> violations = ex.getConstraintViolations();
        String message = violations.stream().map(ConstraintViolation::getMessage).collect(Collectors.joining(";"));
        return R.error(message);
    }

    @ExceptionHandler(SQLException.class)
    public R<?> sqlException(SQLException ex) {
        log.error("SQLException:", ex);
        return R.error(ex.getMessage());
    }

    @ExceptionHandler(HttpMediaTypeNotSupportedException.class)
    public R<?> httpMediaTypeNotSupportedException(HttpMediaTypeNotSupportedException ex) {
        log.error("HttpMediaTypeNotSupportedException:", ex);
        MediaType contentType = ex.getContentType();
        if (contentType != null) {
            return R.error("请求类型(Content-Type)[" + contentType + "] 与实际接口的请求类型不匹配");
        }
        return R.error("无效的Content-Type类型");
    }

    @ExceptionHandler(MissingServletRequestPartException.class)
    public R<?> missingServletRequestPartException(MissingServletRequestPartException ex) {
        log.error("MissingServletRequestPartException:", ex);
        return R.error("请求中必须至少包含一个有效文件");
    }

    @ExceptionHandler(IllegalArgumentException.class)
    public R<?> illegalArgumentException(IllegalArgumentException ex) {
        log.error("IllegalArgumentException:", ex);
        return R.error("无效参数异常");
    }

    @ExceptionHandler(NullPointerException.class)
    public R<?> nullPointerException(NullPointerException ex) {
        log.error("NullPointerException:", ex);
        return R.error("空指针异常");
    }

    @ExceptionHandler(MissingServletRequestParameterException.class)
    public R<?> missingServletRequestParameterException(MissingServletRequestParameterException ex) {
        log.error("MissingServletRequestParameterException:", ex);
        return R.error("缺少必须的[" + ex.getParameterType() + "]类型的参数[" + ex.getParameterName() + "]");
    }

    @ExceptionHandler(IllegalStateException.class)
    public R<?> illegalStateException(IllegalStateException ex) {
        log.error("IllegalStateException:", ex);
        return R.error("无效参数异常");
    }

    @ExceptionHandler(MethodArgumentTypeMismatchException.class)
    public R<?> methodArgumentTypeMismatchException(MethodArgumentTypeMismatchException ex) {
        log.warn("MethodArgumentTypeMismatchException:", ex);
        String msg = "参数：[" + ex.getName() + "]的传入值：[" + ex.getValue() +
                "]与预期的字段类型：[" + Objects.requireNonNull(ex.getRequiredType()).getName() + "]不匹配";
        return R.error(msg);
    }

    @ExceptionHandler(BindException.class)
    public R<?> bindException(BindException ex) {
        log.warn("BindException:", ex);
        try {
            String msg = Objects.requireNonNull(ex.getBindingResult().getFieldError()).getDefaultMessage();
            if (StrUtil.isNotEmpty(msg)) {
                return R.error(msg);
            }
        } catch (Exception ee) {
            log.debug("获取异常描述失败", ee);
        }
        StringBuilder msg = new StringBuilder();
        List<FieldError> fieldErrors = ex.getFieldErrors();
        fieldErrors.forEach((oe) ->
                msg.append("参数:[").append(oe.getObjectName())
                        .append(".").append(oe.getField())
                        .append("]的传入值:[").append(oe.getRejectedValue()).append("]与预期的字段类型不匹配.")
        );
        return R.error(msg.toString());
    }

    @ExceptionHandler(HttpMessageNotReadableException.class)
    public R<?> httpMessageNotReadableException(HttpMessageNotReadableException ex) {
        log.warn("HttpMessageNotReadableException:", ex);
        String message = ex.getMessage();
        if (StrUtil.containsAny(message, "Could not read document:")) {
            String msg = String.format("无法正确的解析json类型的参数：%s", StrUtil.subBetween(message, "Could not read document:", " at "));
            return R.error(msg);
        }
        return R.error("参数类型解析异常");
    }

    @ExceptionHandler(DataIntegrityViolationException.class)
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    public R<?> dataIntegrityViolationException(DataIntegrityViolationException ex) {
        log.warn("DataIntegrityViolationException:", ex);
        return R.error("运行SQL出现异常");
    }

}
